﻿using System;
using System.Collections.Generic;
using System.Xml.Linq;
using System.Xml;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using XmlStudio.DataLayer;
using Microsoft.XmlDiffPatch;
using System.Linq;
//using Microsoft.XmlDiffPatch;

namespace XmlStudio.Utilities {
    /// <summary>
    /// Extension methods for XmlStudio.
    /// </summary>
    public static class MyExtensions {
        /// <summary>
        /// Parses string with range definition.
        /// </summary>
        /// <param name="rangeString">String with range, e.g. 1-10,12-15,16</param>
        /// <returns>Return IEnumerable with ints representing individual numbers in desired range.</returns>
        /// <exception cref="System.ArgumentException">When rangeString has bad format.</exception>
        public static IEnumerable<int> ParseRange(this string rangeString) {
            var res = new List<int>();

            if(string.IsNullOrEmpty(rangeString)) {
                return res;
            }

            foreach(var s in rangeString.Split(',')) {
                //try and get the number
                int num;
                if(int.TryParse(s, out num)) {
                    if(num > 0) {
                        res.Add(num);
                    } else {
                        throw new ArgumentException("Wrong input string, only positive numbers are allowed.");
                    }
                } else {
                    // otherwise we might have a range
                    // split on the range delimiter
                    var subs = s.Split('-');
                    int start = 0, end = 0;

                    //now see if we can parse a start and end
                    if(subs.Length > 1 && int.TryParse(subs[0], out start) && int.TryParse(subs[1], out end) && end > start) {
                        if(start <= 0 || end <= 0) {
                            throw new ArgumentException("Wrong input string, only positive numbers are allowed.");
                        }

                        //create a range between the two values
                        res.AddRange(Enumerable.Range(start, end - start + 1));
                    } else if(end == start && end > 0) {
                        res.Add(start);
                    } else {
                        throw new ArgumentException("Wrong input string.");
                    }
                }
            }

            res.Sort();
            return res.Distinct();
        }

        /// <summary>
        /// Gets the <see cref="XElement"/> from the <see cref="XmlNode"/>.
        /// </summary>
        /// <param name="node"><see cref="XmlNode"/> on which this is called.</param>
        /// <returns>Created <see cref="XElement"/>.</returns>
        public static XElement GetXElement(this XmlNode node) {
            XDocument xDoc = new XDocument();
            using(XmlWriter xmlWriter = xDoc.CreateWriter())
                node.WriteTo(xmlWriter);
            return xDoc.Root;
        }

        /// <summary>
        /// Gets <see cref="XmlNode"/> from the <see cref="XElement"/>.
        /// </summary>
        /// <param name="element"><see cref="XElement"/> on which this is called.</param>
        /// <returns>Created <see cref="XmlNode"/>.</returns>
        public static XmlNode GetXmlNode(this XElement element) {
            using(XmlReader xmlReader = element.CreateReader()) {
                XmlDocument xmlDoc = new XmlDocument();
                xmlDoc.Load(xmlReader);
                return xmlDoc;
            }
        }

        /// <summary>
        /// Inserts <see cref="BigXmlNode"/> after specified <see cref="BigXmlNode"/> in the collection of <see cref="BigXmlNode"/>
        /// instances.
        /// </summary>
        /// <param name="list">Extended object.</param>
        /// <param name="node">Node to insert.</param>
        /// <param name="refNode">Reference node.</param>
        public static void InsertAfter(this IList<BigXmlNode> list, BigXmlNode node, BigXmlNode refNode) {
            int index = list.IndexOf(refNode);
            // int index = list.FindIndex(x => x == refNode);
            if(index >= 0) {
                list.Insert(index + 1, node);
            } else {
                throw new InvalidOperationException("refNode is not present in the collection");
            }
        }

        /// <summary>
        /// Performs deep copy of the specified object.
        /// </summary>
        /// <typeparam name="T">Type of the object to deep clone.</typeparam>
        /// <param name="a">Object to deep clone.</param>
        /// <returns>Deep cloned object.</returns>
        public static T DeepClone<T>(this T a) {
            using(MemoryStream stream = new MemoryStream()) {
                BinaryFormatter formatter = new BinaryFormatter();
                formatter.Serialize(stream, a);
                stream.Position = 0;
                return (T)formatter.Deserialize(stream);
            }
        }

        /// <summary>
        /// Compares two xml files.
        /// </summary>
        /// <param name="file1">First file to compare.</param>
        /// <param name="file2">Second file to compare.</param>
        /// <returns>True if files are identical, false otherwise.</returns>
        public static bool XmlCompare(this string file1, string file2) {
            var diffOptions = XmlDiffOptions.None;
            diffOptions |= XmlDiffOptions.IgnoreComments;
            diffOptions |= XmlDiffOptions.IgnoreWhitespace;
            diffOptions |= XmlDiffOptions.IgnoreXmlDecl;

            return XmlCompare(file1, file2, diffOptions, XmlDiffAlgorithm.Auto);
        }

        /// <summary>
        /// Compares two xml files.
        /// </summary>
        /// <param name="file1">First file to compare.</param>
        /// <param name="file2">Second file to compare.</param>
        /// <param name="diffOptions">Diff options.</param>
        /// <returns>True if files are identical, false otherwise.</returns>
        public static bool XmlCompare(this string file1, string file2, XmlDiffOptions diffOptions) {
            return XmlCompare(file1, file2, diffOptions, XmlDiffAlgorithm.Auto);
        }

        /// <summary>
        /// Compares two xml files.
        /// </summary>
        /// <param name="file1">First file to compare.</param>
        /// <param name="file2">Second file to compare.</param>
        /// <param name="diffOptions">Diff options.</param>
        /// <param name="algorithm">Diff algorithm.</param>
        /// <returns>True if files are identical, false otherwise.</returns>
        public static bool XmlCompare(this string file1, string file2, XmlDiffOptions diffOptions, XmlDiffAlgorithm algorithm) {
            var diff = new XmlDiff();
            diff.Algorithm = algorithm;
            diff.Options = diffOptions;

            return diff.Compare(file1, file2, false);
        }

        /// <summary>
        /// Compares two files for equality.
        /// </summary>
        /// <param name="file1">Path to the first file to compare.</param>
        /// <param name="file2">Path to the second file to compare.</param>
        /// <returns>True if files are identical, false otherwise.</returns>
        public static bool FileCompare(this string file1, string file2) {
            int file1byte;
            int file2byte;
            FileStream fs1;
            FileStream fs2;

            // Determine if the same file was referenced two times.
            if(file1 == file2) {
                // Return true to indicate that the files are the same.
                return true;
            }

            // Open the two files.
            fs1 = new FileStream(file1, FileMode.Open);
            fs2 = new FileStream(file2, FileMode.Open);

            // Check the file sizes. If they are not the same, the files 
            // are not the same.
            if(fs1.Length != fs2.Length) {
                // Close the file
                fs1.Close();
                fs2.Close();

                // Return false to indicate files are different
                return false;
            }

            // Read and compare a byte from each file until either a
            // non-matching set of bytes is found or until the end of
            // file1 is reached.
            do {
                // Read one byte from each file.
                file1byte = fs1.ReadByte();
                file2byte = fs2.ReadByte();
            }
            while((file1byte == file2byte) && (file1byte != -1));

            // Close the files.
            fs1.Close();
            fs2.Close();

            // Return the success of the comparison. "file1byte" is 
            // equal to "file2byte" at this point only if the files are 
            // the same.
            return ((file1byte - file2byte) == 0);
        }
    }
}
